// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:kernel/ast.dart';
import 'package:kernel/clone.dart' show CloneVisitorNotMembers;
import 'package:kernel/type_algebra.dart' show Substitution;
import 'package:kernel/type_environment.dart';

import '../base/messages.dart';
import '../builder/library_builder.dart';

/// Data for clone default values for synthesized function nodes once the
/// original default values have been computed.
///
/// This is used for constructors in unnamed mixin application, which are
/// created from the constructors in the superclass, and for tear off lowerings
/// for redirecting factories, which are created from the effective target
/// constructor.
class DelayedDefaultValueCloner {
  /// The original constructor or procedure.
  final Member original;

  /// The synthesized constructor or procedure.
  final Member synthesized;

  /// If `true`, the [_synthesized] is guaranteed to have the same parameters in
  /// the same order as [_original]. Otherwise [_original] is only guaranteed to
  /// be callable from [_synthesized], meaning that is has at most the same
  /// number of positional parameters and a, possibly reordered, subset of the
  /// named parameters.
  final bool identicalSignatures;

  final List<int?>? _positionalSuperParameters;

  final List<String>? _namedSuperParameters;

  bool isOutlineNode;

  final LibraryBuilder _libraryBuilder;

  CloneVisitorNotMembers? _cloner;

  /// Set to `true` we default values have been cloned, ensuring that cloning
  /// isn't performed twice.
  bool _hasCloned = false;

  DelayedDefaultValueCloner(
    this.original,
    this.synthesized, {
    this.identicalSignatures = true,
    List<int?>? positionalSuperParameters = null,
    List<String>? namedSuperParameters = null,
    this.isOutlineNode = false,
    required LibraryBuilder libraryBuilder,
  }) : _positionalSuperParameters = positionalSuperParameters,
       _namedSuperParameters = namedSuperParameters,
       _libraryBuilder = libraryBuilder,
       // Check that [positionalSuperParameters] and [namedSuperParameters] are
       // provided or omitted together.
       assert(
         (positionalSuperParameters == null) == (namedSuperParameters == null),
       ),
       assert(
         positionalSuperParameters == null ||
             () {
               // Check that [positionalSuperParameters] is sorted if it's
               // provided. The `null` values are allowed in-between the sorted
               // values.
               for (
                 int i = -1, j = 0;
                 j < positionalSuperParameters.length;
                 j++
               ) {
                 int? currentValue = positionalSuperParameters[j];
                 if (currentValue != null) {
                   if (i == -1 ||
                       positionalSuperParameters[i]! < currentValue) {
                     i = j;
                   } else {
                     return false;
                   }
                 }
               }
               return true;
             }(),
       ),
       assert(
         namedSuperParameters == null ||
             () {
               // Check that [namedSuperParameters] are the subset of and in the
               // same order as the named parameters of [_synthesized].
               int superParameterIndex = 0;
               for (
                 int namedParameterIndex = 0;
                 namedParameterIndex <
                         synthesized.function!.namedParameters.length &&
                     superParameterIndex < namedSuperParameters.length;
                 namedParameterIndex++
               ) {
                 if (synthesized
                         .function!
                         .namedParameters[namedParameterIndex]
                         .name ==
                     namedSuperParameters[superParameterIndex]) {
                   ++superParameterIndex;
                 }
               }
               return superParameterIndex == namedSuperParameters.length;
             }(),
       );

  void cloneDefaultValues(TypeEnvironment typeEnvironment) {
    if (_hasCloned) return;

    // TODO(ahe): It is unclear if it is legal to use type parameters in
    // default values, but Fasta is currently allowing it, and the VM
    // accepts it. If it isn't legal, the we can speed this up by using a
    // single cloner without substitution.

    // For mixin application constructors, the argument count is the same, but
    // for redirecting tear off lowerings, the argument count of the tear off
    // can be less than that of the redirection target or, in errors cases, be
    // unrelated.

    FunctionNode _original = original.function!;
    FunctionNode _synthesized = synthesized.function!;

    if (identicalSignatures) {
      assert(
        _positionalSuperParameters != null ||
            _synthesized.positionalParameters.length ==
                _original.positionalParameters.length,
      );
      List<int?>? positionalSuperParameters = _positionalSuperParameters;
      for (int i = 0; i < _original.positionalParameters.length; i++) {
        if (positionalSuperParameters == null) {
          _cloneInitializer(
            _original.positionalParameters[i],
            _synthesized.positionalParameters[i],
          );
        } else if (i < positionalSuperParameters.length) {
          int? superParameterIndex = positionalSuperParameters[i];
          if (superParameterIndex != null) {
            VariableDeclaration originalParameter =
                _original.positionalParameters[i];
            VariableDeclaration synthesizedParameter =
                _synthesized.positionalParameters[superParameterIndex];
            _cloneDefaultValueForSuperParameters(
              originalParameter,
              synthesizedParameter,
              typeEnvironment,
              isOptional:
                  superParameterIndex >= _synthesized.requiredParameterCount,
            );
          }
        }
      }

      assert(
        _namedSuperParameters != null ||
            _synthesized.namedParameters.length ==
                _original.namedParameters.length,
      );
      List<String>? namedSuperParameters = _namedSuperParameters;
      int superParameterNameIndex = 0;
      Map<String, int> originalNamedParameterIndices = {};
      for (int i = 0; i < _original.namedParameters.length; i++) {
        originalNamedParameterIndices[_original.namedParameters[i].name!] = i;
      }
      for (int i = 0; i < _synthesized.namedParameters.length; i++) {
        if (namedSuperParameters == null) {
          _cloneInitializer(
            _original.namedParameters[i],
            _synthesized.namedParameters[i],
          );
        } else if (superParameterNameIndex < namedSuperParameters.length &&
            namedSuperParameters[superParameterNameIndex] ==
                _synthesized.namedParameters[i].name) {
          String superParameterName =
              namedSuperParameters[superParameterNameIndex];
          int? originalNamedParameterIndex =
              originalNamedParameterIndices[superParameterName];
          if (originalNamedParameterIndex != null) {
            VariableDeclaration originalParameter =
                _original.namedParameters[originalNamedParameterIndex];
            VariableDeclaration synthesizedParameter =
                _synthesized.namedParameters[i];
            _cloneDefaultValueForSuperParameters(
              originalParameter,
              synthesizedParameter,
              typeEnvironment,
              isOptional: !synthesizedParameter.isRequired,
            );
          } else {
            // TODO(cstefantsova): Handle the erroneous case of missing names.
          }
          superParameterNameIndex++;
        }
      }
    } else {
      for (int i = 0; i < _synthesized.positionalParameters.length; i++) {
        VariableDeclaration synthesizedParameter =
            _synthesized.positionalParameters[i];
        if (i < _original.positionalParameters.length) {
          if (i >= _synthesized.requiredParameterCount) {
            if (i < _original.requiredParameterCount) {
              // Coverage-ignore-block(suite): Not run.
              // Error case: use `null` as initializer.
              synthesizedParameter.initializer = new NullLiteral()
                ..parent = synthesizedParameter;
              if (synthesizedParameter.type.nullability !=
                  Nullability.nullable) {
                synthesizedParameter.isErroneouslyInitialized = true;
              }
            } else {
              _cloneInitializer(
                _original.positionalParameters[i],
                synthesizedParameter,
              );
            }
          }
        } else {
          if (i >= _synthesized.requiredParameterCount) {
            // Error case: use `null` as initializer.
            synthesizedParameter.initializer = new NullLiteral()
              ..parent = synthesizedParameter;
            if (synthesizedParameter.type.nullability != Nullability.nullable) {
              // Coverage-ignore-block(suite): Not run.
              synthesizedParameter.isErroneouslyInitialized = true;
            }
          }
        }
      }
      if (_synthesized.namedParameters.isNotEmpty) {
        Map<String, VariableDeclaration> originalParameters = {};
        for (int i = 0; i < _original.namedParameters.length; i++) {
          originalParameters[_original.namedParameters[i].name!] =
              _original.namedParameters[i];
        }
        for (int i = 0; i < _synthesized.namedParameters.length; i++) {
          VariableDeclaration synthesizedParameter =
              _synthesized.namedParameters[i];
          VariableDeclaration? originalParameter =
              originalParameters[synthesizedParameter.name!];
          if (originalParameter != null) {
            if (!originalParameter.isRequired &&
                !synthesizedParameter.isRequired) {
              _cloneInitializer(originalParameter, synthesizedParameter);
            }
          } else {
            if (!synthesizedParameter.isRequired) {
              // Error case: use `null` as initializer.
              synthesizedParameter.initializer = new NullLiteral()
                ..parent = synthesizedParameter;
              if (synthesizedParameter.type.nullability !=
                  Nullability.nullable) {
                synthesizedParameter.isErroneouslyInitialized = true;
              }
            }
          }
        }
      }
    }
    _hasCloned = true;
  }

  void _cloneInitializer(
    VariableDeclaration originalParameter,
    VariableDeclaration clonedParameter,
  ) {
    if (originalParameter.initializer != null) {
      CloneVisitorNotMembers cloner = _cloner ??= new CloneVisitorNotMembers();
      clonedParameter.initializer = cloner.clone(originalParameter.initializer!)
        ..parent = clonedParameter;
    }
    clonedParameter.isErroneouslyInitialized |=
        originalParameter.isErroneouslyInitialized;
  }

  void _cloneDefaultValueForSuperParameters(
    VariableDeclaration originalParameter,
    VariableDeclaration synthesizedParameter,
    TypeEnvironment typeEnvironment, {
    required bool isOptional,
  }) {
    Expression? originalParameterInitializer = originalParameter.initializer;
    DartType? originalParameterInitializerType = originalParameterInitializer
        ?.getStaticType(new StaticTypeContext(synthesized, typeEnvironment));
    DartType synthesizedParameterType = synthesizedParameter.type;
    if (originalParameterInitializerType != null &&
        typeEnvironment.isSubtypeOf(
          originalParameterInitializerType,
          synthesizedParameterType,
        )) {
      _cloneInitializer(originalParameter, synthesizedParameter);
    } else if (originalParameterInitializer == null && isOptional) {
      synthesizedParameter.initializer = new NullLiteral()
        ..parent = synthesizedParameter;
    } else {
      synthesizedParameter.hasDeclaredInitializer = false;
      if (synthesizedParameterType.isPotentiallyNonNullable) {
        _libraryBuilder.addProblem(
          codeOptionalSuperParameterWithoutInitializer.withArgumentsOld(
            synthesizedParameter.type,
            synthesizedParameter.name!,
          ),
          synthesizedParameter.fileOffset,
          synthesizedParameter.name?.length ?? 1,
          synthesized.fileUri,
        );
        synthesizedParameter.isErroneouslyInitialized = true;
      }
    }
  }

  @override
  String toString() {
    return "DelayedDefaultValueCloner(original=${original}, "
        "synthesized=${synthesized})";
  }
}

class TypeDependency {
  final Member synthesized;
  final Member original;
  final Substitution substitution;
  final bool copyReturnType;
  bool _hasBeenInferred = false;

  TypeDependency(
    this.synthesized,
    this.original,
    this.substitution, {
    required this.copyReturnType,
  });

  void copyInferred() {
    if (_hasBeenInferred) return;
    for (int i = 0; i < original.function!.positionalParameters.length; i++) {
      VariableDeclaration synthesizedParameter =
          synthesized.function!.positionalParameters[i];
      VariableDeclaration originalParameter =
          original.function!.positionalParameters[i];
      synthesizedParameter.type = substitution.substituteType(
        originalParameter.type,
      );
      if (!synthesizedParameter.hasDeclaredInitializer) {
        synthesizedParameter.hasDeclaredInitializer =
            originalParameter.hasDeclaredInitializer;
      }
    }
    for (int i = 0; i < original.function!.namedParameters.length; i++) {
      VariableDeclaration synthesizedParameter =
          synthesized.function!.namedParameters[i];
      VariableDeclaration originalParameter =
          original.function!.namedParameters[i];
      synthesizedParameter.type = substitution.substituteType(
        originalParameter.type,
      );
      if (!synthesizedParameter.hasDeclaredInitializer) {
        synthesizedParameter.hasDeclaredInitializer =
            originalParameter.hasDeclaredInitializer;
      }
    }
    if (copyReturnType) {
      synthesized.function!.returnType = substitution.substituteType(
        original.function!.returnType,
      );
    }
    _hasBeenInferred = true;
  }
}

// Coverage-ignore(suite): Not run.
/// Copies properties, function parameters and body from the [augmentation]
/// procedure to its [origin].
void finishProcedureAugmentation(Procedure origin, Procedure augmentation) {
  origin.fileUri = augmentation.fileUri;
  origin.fileStartOffset = augmentation.fileStartOffset;
  origin.fileOffset = augmentation.fileOffset;
  origin.fileEndOffset = augmentation.fileEndOffset;

  origin.isAbstract = augmentation.isAbstract;
  origin.isExternal = augmentation.isExternal;
  origin.function = augmentation.function;
  origin.function.parent = origin;
}
